#include "ServerLogic.h"
#include "StorageEngine.h"

#include <sstream>
#include <algorithm>
#include <map>

using namespace ServerLogic;

POPException::POPException(std::string message):
Generic::Exception(message){
};

POPAuthFailedException::POPAuthFailedException(std::string message):
POPException(message){
};

POPMaildropLockException::POPMaildropLockException(std::string message):
POPException(message){
};

POPNoSuchMessageException::POPNoSuchMessageException(std::string message):
POPException(message){
};

POPInvalidMailManagerException::POPInvalidMailManagerException(std::string message):
POPException(message){
};

POPInvalidDataFeedException::POPInvalidDataFeedException(std::string message):
POPException(message){
};


MaildropInfo::MaildropInfo():
Messages(0), Octets(0){
};

MailStub::MailStub(int msg, int size):
_msg(msg), _size(size){
};

int MailStub::Msg(){
	return _msg;
};

int MailStub::Size(){
	return _size;
};

Mail::Mail(std::string &body):
_body(body){
};

std::string Mail::Body(){
	return _body;
};

UserAccount::UserAccount(std::string user, std::string pass, ServerLogic::IMailManager *manager):
_user(user),
_pass(pass),
_manager(manager){

	if (_manager == NULL){
		throw POPInvalidMailManagerException("Mail manager empty!");
	};
};

bool UserAccount::Authenticate(std::string pass){
	_authenticated = (pass == _pass);
	return _authenticated;
};

IMailManager * UserAccount::GetManager(){
	if (_authenticated)
		return _manager;
	else
		throw POPAuthFailedException("Must authenticate first!");
};

Session::Session(DataFeeds::IDataFeed *feed):
_feed(feed),
_state(Authorisation),
_quit_invoked(false){

	if (_feed == NULL)
		throw POPInvalidDataFeedException("Data feed invalid!");
};

void Session::Run(){
	std::string command;
	_OK("PopZilla here...");
	do {
		command = _feed->GetData(true);
		ServerCore::Execute(command, this);
	} while (!_quit_invoked);
	//TODO: Update session
	_feed->Close();
};

void Session::USER(std::string user){
	if (_state == Authorisation){
		try {
			UserAccount * u = StorageEngine::Storage::Instance()->GetAccount(user);
			_user = u;
			_OK("Now state your password with PASS");
		} catch (StorageEngine::StorageException e){
			_feed->Reply("-ERR " + e.Message());
		};
	} else {
		_WrongState();
	};
};

void Session::PASS(std::string pass){
	if (_state == Authorisation){
		if (_user != NULL){
			if (!_user->Authenticate(pass)){
				_feed->Reply("-ERR Access denied");
			} else {
				_OK();
				_state = Transaction;
			};
		} else {
			_feed->Reply("-ERR Issue USER command first");
		};
	} else {
		_WrongState();
	};
};

void Session::STAT(){
	char message[200];
	if (_state != Transaction){
		_WrongState();
	} else {
		if (_user != NULL) {
			try {
				MaildropInfo info = _user->GetManager()->Stat();
				sprintf_s(message, "+OK %u %u", info.Messages, info.Octets);
				_feed->Reply(message);
			} catch (Generic::Exception e){
				_feed->Reply("-ERR " + e.Message());
			}
		} else {
			_feed->Reply("-ERR Could not perform command!");
		};
	};
};

void Session::LIST(){
	if (_state == Transaction){
		STAT();
		char message[100];
		MailList list = _user->GetManager()->ListAll();
		for (size_t i = 0; i < list.size(); i++){
			sprintf_s(message, "%u %u", list[i].Msg(), list[i].Size());
			_feed->Reply(message);
		};
		_Dot();
	} else {
		_WrongState();
	}
};

void Session::LIST(int msg){
	if (_state == Transaction) {
		char message[100];
		try {
			MailStub stub = _user->GetManager()->List(msg);
			sprintf_s(message, "%u %u", stub.Msg(), stub.Size());
			_OK(message);
		} catch (POPException e) {
			_ERR(e.Message());
		};
	} else {
		_WrongState();
	};
};

void Session::DELE(int msg){
	if (_state == Transaction){
		try {
			_user->GetManager()->Delete(msg);
			_OK("Message scheduled for removal");
		} catch (POPException e) {
			_ERR(e.Message());
		};
	} else {
		_WrongState();
	};
};

void Session::RSET(){
	if (_state == Transaction){
		_user->GetManager()->Reset();
	} else {
		_WrongState();
	};
};

void Session::QUIT(){
	if (_state != Update){
		_OK("Closing connection");
		_quit_invoked = true;
		_state = Update;
	};
};

void Session::RETR(int msg){
	if (_state == Transaction){
		try {
			Mail mail = _user->GetManager()->Retrieve(msg);
			_OK();
			_feed->Reply(mail.Body());
			_Dot();
		} catch (POPException e) {
			_ERR(e.Message());
		};
	} else {
		_WrongState();
	};
};

void Session::Unknown(){
	_ERR("Unknown command!");
};

void Session::_WrongState(){
	_ERR("Command disallowed in this state!");
};

void Session::_OK(std::string message){
	_feed->Reply("+OK " + message);
};

void Session::_ERR(std::string message){
	_feed->Reply("-ERR " + message);
};

void Session::_Dot(){
	_feed->Reply(".");
};



/********************************
	Server	Core
********************************/

DWORD WINAPI ServerCore::_StartSession (LPVOID param){
	Session * s = (Session *) param;
	s->Run();
	return 1;
};

void ServerCore::Start(){
	DataFeeds::IDataFeed * feed;

	DataFeeds::FeedManager manager;
	manager.EnableAllFeeds();
	DWORD dumpExitCode;

	std::map<Session *, HANDLE> sessions;
	Session * session;
	HANDLE hThread;

	while (true){
		while (!manager.FeedReady()){
			Sleep(50);
		}
		feed = manager.GetFeed();
		if (feed != NULL){
			// Tworzymy nowy watek
			session = new Session(feed);
			hThread = CreateThread(NULL, 0, &ServerCore::_StartSession, (LPVOID) session, 0, &dumpExitCode);
			sessions[session] = hThread;
		}
	}
}

std::vector <std::string> ServerCore::_Parts(std::string s){
	std::stringstream stream;
	std::string word;
	std::vector <std::string> parts;
	size_t from, to;
	from = s.find_first_not_of(" \n\r\t");
	if (from == std::string::npos)
		// Skoro nie ma niebialych znakow
		return parts;
	to = s.find_last_not_of(" \n\r\t");

	stream.str(s.substr(from, to - from + 1));

	while (!stream.eof()){
		stream >> word;
		parts.push_back(word);
	};
	return parts;
};

void ServerCore::_ToLower(std::string & message){
	std::transform(message.begin(),	message.end(), message.begin(),	tolower);
};

void ServerCore::Execute(std::string command, ServerLogic::Session *session){
	std::vector <std::string> parts = _Parts(command);
	_ToLower(parts[0]);
	if (parts[0] == "user"){
		session->USER(parts[1]);
	} else if (parts[0] == "pass") {
		session->PASS(parts[1]);
	} else if (parts[0] == "stat") {
		session->STAT();
	} else if (parts[0] == "list") {
		if (parts.size() > 1) {
			int msg = atoi(parts[1].c_str());
			session->LIST(msg);
		} else {
			session->LIST();
		};
	} else if (parts[0] == "dele") {
		session->DELE(atoi(parts[1].c_str()));
	} else if (parts[0] == "rset") {
		session->RSET();
	} else if (parts[0] == "retr") {
		session->RETR(atoi(parts[1].c_str()));
	} else if (parts[0] == "quit") {
		session->QUIT();
	} else {
		session->Unknown();
	};
};